package msg;

import static org.junit.Assert.*;

import java.util.*;

import msg.visitors.MessageToJson;

import org.json.JSONObject;
import org.junit.Test;

import user.*;

import common.DBTest;
import common.dal.Transaction;
import common.exceptions.*;
import common.interfaces.IPersistent;

@SuppressWarnings({"static-method", "javadoc"})
public class DBMessagePersistencyManagerTest extends DBTest {
	
	private static DBMessagePersistencyManager	$;
	
	@Override
	protected IPersistent setPersistent(Transaction t) {
		$ = new DBMessagePersistencyManager(t);
		return $;
	}
	
	@Override
	protected IPersistent getPersistent() {
		return $;
	}
	
	private Message createMessage() {
		Location location = new Location(0.5, 1);
		String content = "content";
		long id = 0;
		Message msg = new Message(id, location, content, getUser());
		return msg;
	}
	
	@Test
	public void testPersistEventMessage() throws Exception {
		Message msg = createMessage();
		EventMessage eMsg = new EventMessage(msg.getId(), msg.getLocation(), msg.getContent(), msg.getPoster(),
				Calendar.getInstance(), 10);
		$.store(eMsg);
		assertEquals(eMsg, $.get(eMsg.getId()));
	}
	
	@Test
	public void testPersistEventMessageAsMessage() throws Exception {
		Message msg = createMessage();
		Message eMsg = new EventMessage(msg.getId(), msg.getLocation(), msg.getContent(), msg.getPoster(),
				Calendar.getInstance(), 10);
		$.store(eMsg);
		assertEquals(eMsg, $.get(eMsg.getId()));
	}
	
	@Test(expected = InvalidUserException.class)
	public void testPersistMessageFakeUser() throws Exception {
		Location location = new Location(0, 0);
		String content = "";
		long id = 0;
		Message msg = new Message(id, location, content, getFakeUser());
		$.store(msg);
	}
	
	@Test(expected = InvalidUserException.class)
	public void testPersistEventFakeUser() throws Exception {
		Location location = new Location(0, 0);
		String content = "";
		long id = 0;
		EventMessage msg = new EventMessage(id, location, content, getFakeUser(), Calendar.getInstance(), 1);
		$.store(msg);
	}
	
	@Test
	public void testPersistMessage() throws Exception {
		Message msg = createMessage();
		$.store(msg);
		MessageToJson jsoner = new MessageToJson();
		JSONObject expected = msg.accept(jsoner);
		JSONObject actual = $.get(msg.getId()).accept(jsoner);
		assertEquals(expected, actual);
	}
	
	@Test(expected = ExistingEntityException.class)
	public void testPersistMessageDouble() throws Exception {
		Message msg = createMessage();
		$.store(msg);
		$.store(msg);
	}
	
	@Test(expected = ExistingEntityException.class)
	public void testPersistMessageAndEventDouble() throws Exception {
		Message msg = createMessage();
		EventMessage event = new EventMessage(msg.getId(), msg.getLocation(), "", getUser(), getTomorrow(), 10);
		$.store(msg);
		$.store(event);
	}
	
	@Test(expected = InvalidKeyException.class)
	public void testGetDoesntExist() throws Exception {
		$.get(0L);
	}
	
	@Test(expected = InvalidUserException.class)
	public void testGetAllUserDoesntExist() throws Exception {
		$.getAllUserPosts(getFakeUser());
	}
	
	@Test
	public void testGetAllUserEmpty() throws Exception {
		assertTrue($.getAllUserPosts(getUser()).isEmpty());
	}
	
	@Test
	public void testGetAllUser() throws Exception {
		Message msg = createMessage();
		Message msg2 = new Message(msg.getId() + 1, msg.getLocation(), "second content", msg.getPoster());
		Set<Message> allMsgs = new HashSet<>(Arrays.asList(msg, msg2));
		for (Message message: allMsgs) {
			$.store(message);
		}
		Set<Message> result = new HashSet<>($.getAllUserPosts(getUser()));
		assertEquals(allMsgs, result);
	}
	
	@Test
	public void testGetAllUserEvents() throws Exception {
		Message msg = createMessage();
		EventMessage event = new EventMessage(msg.getId() + 1, msg.getLocation(), "second content",
				msg.getPoster(), Calendar.getInstance(), 10);
		Set<EventMessage> allMsgs = new HashSet<>(Arrays.asList(event));
		for (EventMessage message: allMsgs) {
			$.store(message);
		}
		Set<EventMessage> result = new HashSet<>($.getAllUserEvents(msg.getPoster(), null, true));
		assertEquals(allMsgs, result);
	}
	
	@Test
	public void testGetAll() throws Exception {
		User newUser = LoginManager.getNewInstance(t)
				.registerUser(getUser().getLogin() + "__", "other user", PASSWORD);
		try {
			Message msg = createMessage();
			Message msg2 = new Message(msg.getId() + 1, msg.getLocation(), "second content", msg.getPoster());
			Message msg3 = new Message(msg.getId() + 1, msg.getLocation(), "second content", msg.getPoster());
			Set<Message> allMsgs = new HashSet<>(Arrays.asList(msg, msg2, msg3));
			for (Message message: allMsgs) {
				$.store(message);
			}
			Set<Message> result = new HashSet<>($.getAll());
			assertEquals(allMsgs, result);
		} finally {
			LoginManager.getNewInstance(t).deleteUser(newUser.getLogin(), PASSWORD);
		}
	}
	
	@Test(expected = InvalidUserException.class)
	public void testRemoveAllUserMessagesDoesntExist() throws Exception {
		$.removeAllUserMessagesAndEvents(getFakeUser());
	}
	
	@Test
	public void testRemoveAllUserMessages() throws Exception {
		Message msg = createMessage();
		Message msg2 = new Message(msg.getId() + 1, msg.getLocation(), "second content", msg.getPoster());
		Set<Message> allMsgs = new HashSet<>(Arrays.asList(msg, msg2));
		for (Message message: allMsgs) {
			$.store(message);
		}
		Set<Message> result = new HashSet<>($.getAllUserPosts(getUser()));
		assertEquals(allMsgs, result);
		$.removeAllUserMessagesAndEvents(getUser());
		assertTrue($.getAllUserPosts(getUser()).isEmpty());
	}
	
	@Test
	public void testLastIndex() throws Exception {
		Message msg = createMessage();
		Message msg2 = new Message(msg.getId() + 1, msg.getLocation(), "second content", msg.getPoster());
		Set<Message> allMsgs = new HashSet<>(Arrays.asList(msg, msg2));
		for (Message message: allMsgs) {
			$.store(message);
		}
		assertEquals(msg2.getId(), (Long)$.getLastId());
	}
	
	@Test
	public void testDelete() throws Exception {
		Message msg = createMessage();
		$.store(msg);
		assertTrue($.exists(msg.getId()));
		$.delete(msg.getId());
		assertTrue($.getAll().isEmpty());
	}
	
	@Test(expected = InvalidKeyException.class)
	public void testGetBadId() throws Exception {
		$.get(10L);
	}
	
	@Test
	public void testGets() throws Exception {
		Message msg = createMessage();
		EventMessage event = new EventMessage(msg.getId() + 1, msg.getLocation(), "", getUser(),
				Calendar.getInstance(), 1);
		$.store(msg);
		$.store(event);
		assertEquals(msg, $.get(msg.getId()));
		assertEquals(event, $.get(event.getId()));
		assertFalse(event.equals($.get(msg.getId())));
		Message fakeEvent = new Message(event.getId(), event.getLocation(), event.getContent(), event.getPoster());
		assertFalse(msg.equals($.get(event.getId())));
		assertFalse(fakeEvent.equals($.get(event.getId())));
		assertFalse(fakeEvent.equals($.get(event.getId())));
	}
	
	@Test
	public void testGetAllMessageByRadius() throws Exception {
		// make sure no events are considered
		EventMessage event = new EventMessage(0, new Location(0, 0), "event", getUser(), getTomorrow(), 10);
		$.store(event);
		assertEquals(Collections.EMPTY_SET, new HashSet<>(
				$.getAllMessagesInRadius(event.getLocation(), Double.MAX_VALUE, null)));
		final double distance = 81 * 1000;
		Message haifaMsg = new Message(event.getId() + 1, haifa, "Haifa", getUser());
		Message taMsg = new Message(haifaMsg.getId() + 1, telAviv, "TeL-Aviv", getUser());
		$.store(haifaMsg);
		$.store(taMsg);
		assertEquals(new HashSet<>(Arrays.asList(haifaMsg)), new HashSet<>(
				$.getAllMessagesInRadius(haifa, distance - 5000, null)));
		assertEquals(new HashSet<>(Arrays.asList(haifaMsg, taMsg)), new HashSet<>(
				$.getAllMessagesInRadius(haifa, distance + 5000, null)));
	}
	
	@Test
	public void testGetAllEventsByRadius() throws Exception {
		assertEquals(Collections.EMPTY_SET, new HashSet<>(
				$.getAllMessagesInRadius(new Location(0, 0), Double.MAX_VALUE)));
		Message msg = createMessage();
		$.store(msg);
		assertEquals(Collections.EMPTY_SET, new HashSet<>(
				$.getAllEventsInRadius(msg.getLocation(), 100, null, true)));
		EventMessage event = new EventMessage(msg.getId() + 1, msg.getLocation(), "event", getUser(),
				getTomorrow(), 10);
		$.store(event);
		Set<EventMessage> expected = new HashSet<>(Arrays.asList(event));
		assertEquals(expected, new HashSet<>($.getAllEventsInRadius(msg.getLocation(), 100, null, true)));
	}
	
	@Test
	public void testAddressSaved() throws Exception {
		Location loc = new Location(0.5, 1, "Address");
		// on message
		Message msg = new Message(0, loc, "msg1", getUser());
		$.store(msg);
		assertEquals(msg.getLocation(), $.get(msg.getId()).getLocation());
		// on event
		Message event = new EventMessage(1, loc, "msg1", getUser(), getTomorrow(), 1);
		$.store(event);
		assertEquals(event.getLocation(), $.get(event.getId()).getLocation());
	}
	
	@Test
	public void testAddressDefaultIsNull() throws Exception {
		Location loc = new Location(0.5, 1, null);
		// on message
		Message msg = new Message(0, loc, "msg1", getUser());
		$.store(msg);
		assertNull($.get(msg.getId()).getLocation().getAddress());
		// on event
		Message event = new EventMessage(1, loc, "msg1", getUser(), getTomorrow(), 1);
		$.store(event);
		assertNull($.get(event.getId()).getLocation().getAddress());
	}
	
	@Test
	public void testSince() throws Exception {
		Location loc = new Location(0.5, 1, null);
		Message msg = new Message(0, loc, "msg1", getUser());
		long start = msg.getTime();
		$.store(msg);
		assertEquals(new HashSet<>(Arrays.asList(msg)), new HashSet<>($.getAllMessagesInRadius(loc, 0, start)));
		Thread.sleep(1);
		assertEquals(Collections.EMPTY_SET, new HashSet<>($.getAllMessagesInRadius(loc, 0, new Date().getTime())));
	}
	
	@Test
	public void testPast() throws Exception {
		EventMessage pastEvent = new EventMessage(0, new Location(0, 0), "past event", getUser(),
				new Date().getTime(), 10);
		$.store(pastEvent);
		EventMessage futureEvent = new EventMessage(pastEvent.getId() + 1, new Location(0, 0), "past event",
				getUser(), getTomorrow(), 10);
		$.store(futureEvent);
		HashSet<EventMessage> expected = new HashSet<>(Arrays.asList(futureEvent));
		Location zero = new Location(0, 0);
		assertEquals(expected, new HashSet<>($.getAllEventsInRadius(zero, 0, null, false)));
		Calendar cal = getTomorrow();
		cal.add(Calendar.HOUR, -5);
		assertEquals(expected, new HashSet<>($.getAllEventsInRadius(zero, 0, cal.getTimeInMillis(), false)));
		assertEquals(expected, new HashSet<>($.getAllEventsInRadius(zero, 0, cal.getTimeInMillis(), true)));
		expected.add(pastEvent);
		assertEquals(expected, new HashSet<>($.getAllEventsInRadius(zero, 0, null, true)));
	}
	
	@Test
	public void testRadiusTrivial() throws Exception {
		Message msg = new Message(0, new Location(1.1, 0.1), "msg", getUser());
		$.store(msg);
		assertEquals(new HashSet<>(Arrays.asList(msg)), new HashSet<>($.getAllMessagesInRadius(new Location(1.1,
				0.1), 0, null)));
	}
	
	@Test
	public void testOnlyMessageSince() throws Exception {
		Message msg1 = createMessage();
		$.store(msg1);
		Thread.sleep(10);
		assertEquals(Collections.EMPTY_SET, new HashSet<>(
				$.getAllUserMessagesOnly(getUser(), new Date().getTime())));
		Thread.sleep(10);
		assertEquals(new HashSet<>(Arrays.asList(msg1)), new HashSet<>($.getAllUserMessagesOnly(getUser(), msg1
				.getTime())));
	}
	
	@Test
	public void testOnlyMessageEventsPast() throws Exception {
		EventMessage event = new EventMessage(0, haifa, "event", getUser(), new Date().getTime(), 10);
		$.store(event);
		Thread.sleep(10);
		assertEquals(Collections.EMPTY_SET, new HashSet<>(
				$.getAllUserMessagesOnly(getUser(), new Date().getTime())));
		Thread.sleep(10);
		assertEquals(new HashSet<>(), new HashSet<>($.getAllUserEvents(getUser(), null, false)));
	}
}
